#include "pmm.h"
#include "list.h"
#include "types.h"// ROUNDUP
#include "riscv.h"//QEMU_MAX_ADDR
#include "panic.h"
#include "string.h"// memset
#include "page.h"// map_a_page()
#include "proc.h"// cur_proc
#include "stdio.h"

// end address of disk (from kernel.lds)
extern char edisk[];

// free page list
static struct free_page_list *frpg_list;

// the start address of pages to be allocated
static uint64 va_start = ALLOC_START;

/* @brief: init frpg_list:link free physical memory into frpg_list
 * QEMU: (edisk, 0x88000000)
 */
void pmm_init() {
	// 0--4095,4096--8191...
	frpg_list = NULL;
	uint64 start = ROUNDUP((uint64)edisk, (uint64)PGSIZE);
	uint64 end = start + PGSIZE;
	while (end <= QEMU_MAX_ADDR) {
		// not using kfree(), because kfree need virtual address as parameter
		// paging mechanism not on yet, we use physical address now
		// free_a_page need physical address as parameter
		free_a_page(start);
		start += PGSIZE;
		end += PGSIZE;
	}
}

/* @brief: alloc a free page sizeof PGSIZE
 * @return: the start address of allocated page
 */
uint64 alloc_a_page() {
	struct linked_list *node = list_rm_front((struct linked_list **)&frpg_list);
	if (node == NULL) {
		panic("alloc_page: no pages left at all\n");
	}
	if((uint64)node % PGSIZE) {
		panic("alloc_page: start address is not PGSIZE aligned\n");
	}
	// clean the allocated page
	memset((char *)node, 0, PGSIZE);
	return (uint64)node;
}

/* @brief: alloc some free pages, these pages may not be continuous physically
 * @param pgnum: the number of pages
 * @return: start address of the allocated pages
 * 	if only alloc 1 page, return physical address
 * 	otherwise, return virtual address
 * @note: if pgnum is 1, won't map
 * 	if pgnum > 1, map these pages to a continuous virtual address
 */
uint64 alloc_pages(int pgnum) {
	int i;
	uint64 pa_start;
	if (pgnum == 1) {
		return alloc_a_page();
	}
	for (i = 0; i < pgnum; ++i) {
		pa_start = alloc_a_page();
		map_a_page(cur_proc->table_ptr3, va_start, pa_start, PTE_U | PTE_W | PTE_R);
		va_start += PGSIZE;
	}
	return (va_start - PGSIZE * pgnum);
}

/* @brief: alloc some space
 * @param size: needed byte number
 * @return: start address(virtual) of the allocated space
 * @note: allocated space uses PGSIZE as unit but not 1 byte
 */
uint64 kalloc(uint64 size) {
	if ((int64)size < 0) {
		panic("kalloc: size can't be negative.");
	}
	// (size - 1) must have ()
	return alloc_pages(ROUNDUP((size - 1), PGSIZE) / PGSIZE);
}

/* @brief: free a page sizeof PGSIZE
 * @param start: start address must be PGSIZE aligned
 * @note: here start address is the physical address
 */
void free_a_page(uint64 start) {
	if(start % PGSIZE) {
		panic("free_page: start address is not PGSIZE aligned.");
	}
	struct linked_list *node = (struct linked_list *)start;
	list_add_front((struct linked_list **)&frpg_list, node);
}

/* @brief: free pages
 * @param start: start address must be PGSIZE aligned
 * @param pgnum: page number
 * @note: start address here is the virtual address
 */
void free_pages(uint64 start, int pgnum) {
	int i;
	uint64 va = start;
	uint64 pa;
	for (i = 0; i < pgnum; ++i) {
		// virtual address to physical address
		pa = va2pa(va, cur_proc->table_ptr3);
		free_a_page(pa);
		va += PGSIZE;
	}
}

/* @brief: free some space
 * @param start: start address(virtual) to be free
 * @param size: bytes number to be free
 * @note: free space uses PGSIZE as unit but not 1 byte
 * 	free page will not clean the page, but alloc will
 */
void kfree(uint64 start, uint64 size) {
	if (start == 0) {
		panic("kfree: start can't be zero\n");
	}
	if ((int64)size <= 0) {
		panic("kfree: size can't be negative or zero\n");
	}
	free_pages(start, ROUNDUP((size - 1), PGSIZE) / PGSIZE);
}
